package org.NooLab.itexx.app.processes;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

import org.NooLab.itexx.ITexxWindowCommons;
import org.NooLab.itexx.app.config.ITexxAppPropertiesIntf;
import org.NooLab.itexx.app.gui.DialogMaster;
import org.NooLab.itexx.app.gui.handlers.keys.Keyhandlers;
import org.NooLab.itexx.app.processes.NlpProcess.InputReader;
import org.NooLab.itexx.app.processes.ProcessAdmin.ProcessSupervisor;
import org.NooLab.itexx.app.processes.comm.CommPort;
import org.NooLab.itexx.app.processes.comm.CommPorts;

import org.NooLab.itexx.comm.messages.CommBoxMessage;
import org.NooLab.itexx.comm.messages.CommBoxMessageIntf;
import org.NooLab.itexx.comm.tcp.box.TcpBox;
import org.NooLab.structures.AppCommBox;
import org.NooLab.structures.nlp.NlpProcessInfo;
import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.net.GUID;
import org.NooLab.utilities.process.ConsoleDetectorInformIntf;
import org.NooLab.utilities.process.DetectionEvent;
import org.NooLab.utilities.process.RememberAllWrittenTextPrintStream;
import org.NooLab.utilities.strings.ArrUtilities;
import org.NooLab.utilities.strings.StringsUtil;



/**
 * 
 * 
 * 
 *
 */
public class ProcessAdmin implements ProcessAdminIntf, ConsoleDetectorInformIntf{

	// keeps simply a list for 
	
	ITexxWindowCommons appCommons ;
	ITexxAppPropertiesIntf appProperties;
	
	ProcessSupervisor supervisorProcess;
	boolean supervisorProcessIsRunning=false;
	
	CommPorts commPorts;
	
	String binPath = "";
	String homepath = "ling"; // D:/data/iTexx/app +  
	String browserHomepath = "browser"; 
	
	String systemRootDir; 
	
	AppCommBox[] commBoxes ;
	private BrowserProcess[] browserProcess ;
	NlpProcess[] nlpProc;
	
	protected PrintLog out;
	protected StringsUtil strgutil = new StringsUtil();
	private RememberAllWrittenTextPrintStream consolePrint;
	public boolean preparationsFinished;
	public boolean inputReaderIsRunning;
	
	
	// ========================================================================
	public ProcessAdmin(ITexxWindowCommons commons) {
		int basePort,cBoxCount;
		
		appCommons = commons;
		appProperties = appCommons.getAppProperties() ;
	
		basePort =  appProperties.getCommPortBase();
		
		// is it free? otherwise check for the next free port
		basePort = checkForFreePort(basePort);
		
		cBoxCount = appProperties.getNumberOfPreparedCommboxes() ;
		
		commBoxes = new AppCommBox[cBoxCount];
		nlpProc = new NlpProcess[cBoxCount] ; 
		browserProcess = new BrowserProcess[cBoxCount] ;
		
		out = appCommons.out ;
		commPorts= new CommPorts(basePort, cBoxCount);
		
		binPath = appProperties.getOwnBinPath();
		
		String apphomepathbasic ;
		// e.g.  D:/data/iTexx/app
		apphomepathbasic = DFutils.getParentDir(binPath);
		homepath = DFutils.createPath( apphomepathbasic,"app/"+ homepath+"/"); // + ling 
		browserHomepath = DFutils.createPath( apphomepathbasic, "app/"+browserHomepath+"/"); // "/browser";

		
		// 
		consolePrint = new RememberAllWrittenTextPrintStream(System.out); // where to put it
		System.setOut(consolePrint);
		// System.setErr(consolePrint);
		
		// consolePrint.setDetectorCallback((ConsoleDetectorInformIntf)this);
		// consolePrint.addDetectorPattern("completed");
		
		// new InputReader();		
	}
	
	// ========================================================================
	

	@Override
	public void setDetectionEvent(DetectionEvent devt) {
		// display on console, but prevent self-response by shortening the string
		// out.printErr(2, "             >>>>>>>>>>>>>>   " + devt.getLastline().substring(0, devt.getLastline().length()-10));
		// setting some flag...
		
		
	}

	public void checkProcessInfo(NlpProcessInfo npi) {
		
		if (npi==null){
			return;
		}
		
		String prc = npi.getProcessCode();
		
		boolean hb ;
		
		if (prc==null){
			hb=true;
		}else{
			hb = prc.toLowerCase().contentEquals("nlp");
		}
		
		if (hb){
			String msg  = npi.getText() ;
			String lang = npi.getLanguageCode() ;
			
			if (msg.contains("ready")){
				appCommons.getModelController().addConfirmedLanguage(lang);
			}
			
		}
		
	}

	class InputReader implements Runnable{

		String line;
		Thread prcInRThrd;
		private String consoleText;
		ArrayList<String> pLines = new ArrayList<String>();
		private int lastReadyPos;
		
		public InputReader(){
			prcInRThrd = new Thread(this,"prcInRThrd") ;
			prcInRThrd.start();
		}
		
		private void detect() throws IOException{
			
	        
	        for (int i=0;i<pLines.size();i++){
	        	
	        	line = pLines.get(i);
	        	
	        	if (line.contains("@iTexx")){ 
	        		// that's a special message to the host == this
	        		// new CoWorkerMsgDigest( itexx,this, CoWorkerIntf._mainServerPort, line);
	        		System.out.println(line);
	        	}else{
	        		System.out.println(line);
	        		
	        		boolean hb;
	        		
	        		hb = line.contains("NLP module for language") || line.contains("completed");
	        		if (hb){
	//if (languagecode.contains("en"))
	{
		int k;
		k=0;
	}
	out.printErr(2, " >>>>>>>>>>>>>>>>>>  message detected...");
	        			hb = line.contains("done");
	        		}
	        		if (hb){
	        			hb = line.contains("ready");
	        		}
	        		if (hb){
	        			preparationsFinished=hb;
	        			DialogMaster dm = appCommons.getDialogMaster();
	        			dm.autofade(true,4000) ;
	        			dm.setMessage(line);
	        			dm.show();
	        			String msg = "NLP module for language <> has been recognized to be available.";
	        			out.printErr(1, msg );
	        			
	        			appCommons.getWindowGui().getMainStatusBar().setTextMessage( msg);
	        		}
	        		
	        	}
	        }// i -> all pLines
	        pLines.clear();
		}
		
		
		@Override
		public void run() {
			// 
			inputReaderIsRunning = true;
			int lc,lastStreamSize = 0, currSize=0;
			
			
			
			while (inputReaderIsRunning) {

				try {
					pLines.clear();
					
					consoleText = consolePrint.getAllWrittenText();
					
					lc = consoleText.length();
					
					if (lastStreamSize < lc) {
						// detect(); // uses registered  callbacks for doing the comparisons
						if (lc>200){
							String str = consoleText.substring(0,120);
if (str.contains("ready to work")){
	out.printErr(2, " >>>>>>>>>>>>>>> DETECTED READY <<<<<<<<<<<<<<<<<<<<<<<<");
}
						}
						// 
					}
					
					lastStreamSize = lc;
					// System.out.println("  >>>>>>>>>>>>>>> length of console out : "+lc) ;
					
					int pr = consoleText.indexOf("completed...");
					if ((lc>100) && (pr>1) && (pr>lc-300) && (lastReadyPos != pr)){
						// out.print(2, " >>>>>>>>>>>>>>> DETECTED READY <<<<<<<<<<<<<<<<<<<<<<<<");
						// out.print(2, consoleText.substring(lc-200, lc-100));
						lastReadyPos = pr; 
					}

					
				} catch (Exception e) {
				}
				out.delay(1);
			}

			inputReaderIsRunning = false;
		}
		
		
		
	}
	// ------------------------------------------
	public void close(){

		supervisorProcessIsRunning = false;
		inputReaderIsRunning = false;
		
		for (int i=0;i<nlpProc.length;i++){
			if (nlpProc[i]!=null){
				nlpProc[i].close();
			}
		}
		
		for (int i=0;i<browserProcess.length;i++){
			if (browserProcess[i]!=null){
				browserProcess[i].close();
			}
		}
		
		for (int i=0;i<commBoxes.length;i++){
			if (commBoxes[i]!=null){
				commBoxes[i].close() ;
			}
		}

		
	}
	
	// started from main "ITexxWindow", the observer sink for messages is defined
	// by the class  "LinguisticsPort"
	public void startSatelliteProcesses() {
		// 
		LinguisticsPort lp; // just for dev jumping
		
		try {
			ArrayList<String> langs = appCommons.getAppProperties().getIniPreferences().getOpenNlpLanguages() ;
			String[] languages = (String[]) ArrUtilities.changeArraystyle(langs);
			
			nlpProcesses( languages) ; // new String[]{"de","en"}) ;
						
		} catch (Exception e) {
			e.printStackTrace();
		} 
		
		startSupervisorProcess();
	}

	public void startExternalNativeBrowserProcess() throws Exception {
	
		CommPort cPort;
		int pin = 7988,pout = 7989 ;
		String[] args ;
		
		args = getBrowsersArgumentsBase();
		
		DFutils.createPath(browserHomepath,"/"); 
		
		cPort = commPorts.getPortsOfSlot( commPorts.getItems().size()-1 );
		
		if (cPort.getPortIn()>1100){
			pin = cPort.getPortIn();
			pout = cPort.getPortOut() ;
		}
		
		startNativeBrowserProcess( args,pin,pout,browserHomepath );
		
	}
	
	private void startSupervisorProcess() {
		// 
		
		supervisorProcess = new ProcessSupervisor(this);
	}

	public void nlpProcesses(String[] languagecodes ) throws Exception{
		
		CommPort cPort ;
		String[] args = new String[8];
		int z=0,pin,pout;
		
		for (int i=0;i<languagecodes.length;i++){
			
			cPort = commPorts.getPortsOfSlot(i);
			
			if (cPort==null){
				continue;
			}
			pin  = cPort.getPortIn() ;
			pout = cPort.getPortOut() ;
			cPort.setName("ling-"+languagecodes[i]);
			args = getArgumentsBase();
			
			startLinguisticsProcess( i, args,languagecodes[i],pin,pout,homepath);
			
			z++;
			out.delay(500);
		}
		
		out.print(2, "number of created nlp processes : "+z);
		pin=0;
	}
	
	
	
	private void startNativeBrowserProcess( String[] args, 
											int pin, int pout,
			  								String homepath ) throws Exception {
		
		
		
		for (int i=0;i<args.length;i++){
			if (args[i].contains("$in$")){
				args[i] = args[i].replace("$in$", ""+pin); 
			}
			if (args[i].contains("$out$")){
				args[i] = args[i].replace("$out$", ""+pout);
			}
			args[i] = args[i].replace(": ", ":") ;
		}// i->

		int index = commBoxes.length-1;
		// browser is always the last one
		commBoxes[index] = prepareTcpBox(pin,pout,"NNB") ;
		
		commBoxes[index].tcpBox.deleteObservers();
		
		LinguisticsPort lp; // just for dev jumping
		commBoxes[index].tcpBox.addTcpObserver( appCommons.getNativeBrowserPort() ) ; 

		
		// starting the process
		browserProcess[0] = new BrowserProcess( this, index, args );
		
		browserProcess[0].create() ;
		browserProcess[0].launch() ;

		
	}
	
	private void startLinguisticsProcess( int index, String[] args, String languagecode, 
										  int pin, int pout,
										  String homepath ) throws Exception {
		
		String name = "ling-"+languagecode;
		
		for (int i=0;i<args.length;i++){
			if (args[i].contains("$lang$")){
				args[i] = args[i].replace("$lang$", languagecode);
			}
			if (args[i].contains("$in$")){
				args[i] = args[i].replace("$in$", ""+pin); 
			}
			if (args[i].contains("$out$")){
				args[i] = args[i].replace("$out$", ""+pout);
			}
			args[i] = args[i].replace(": ", ":") ;
		}// i->
		
		
		// preparing the COmmBox
		
		commBoxes[index] = prepareTcpBox(pin,pout,name) ;
		
		commBoxes[index].tcpBox.deleteObservers();
		
		LinguisticsPort lp; // just for dev jumping
		commBoxes[index].tcpBox.addTcpObserver( appCommons.getLinguisticsPort() ) ; 

		
		// starting the process
		nlpProc[index] = new NlpProcess( this, index, args );
		
		nlpProc[index].setLanguagecode(languagecode);
		nlpProc[index].create() ;
		nlpProc[index].launch() ;
		
		
		// createNlpProcessX(args);
	}
	
	
	
	private void createNlpProcessX(String[] args) throws InterruptedException {
		// 
		
		String appsecret,name,homedir ;
		String ports,argumentStr;
		
		try {
			
			
			String jarpath ;
			
			jarpath = appProperties.getOwnBinPath();
			jarpath = DFutils.createPath(jarpath , "NooLabNLP.jar");
	
			argumentStr =  appCommons.strgutil.arr2text(args, "", "  ");
			
			// TODO this is different on different platforms
			// mac osx folder action: Runtime.getRuntime().exec(new String[] { "open", instFolder.toString() })
			// we probably need the full path to java, which we get by System.property  java.home
			// windows TODO better use String[] instead of a string...
            Process extProcess = Runtime.getRuntime().exec("cmd /c start java -jar "+jarpath +"  "+argumentStr );//  " > D:/temp/~startlog.txt");

			StreamGobbler2 errorGobbler = new StreamGobbler2(extProcess.getErrorStream(), "ERR  ");

			// any output?
			StreamGobbler2 outputGobbler = new StreamGobbler2(extProcess.getInputStream(), "---");

			// kick them off
			errorGobbler.start();
			outputGobbler.start();
			
			int exitVal = extProcess.waitFor();
			System.out.println("ExitValue: " + exitVal);
			
		} catch (IOException e) {
			e.printStackTrace();
		}

		
	}

	private String[]  getBrowsersArgumentsBase(){
		
		String[] args = new String[6];
		
		// these should all come from a resources config file...
		args[0] = "-homedir:\""+browserHomepath+"\"" ; 
		args[1] = "-context:itexx" ;
		args[2] = "-comm: in-$in$,out-$out$" ;
		args[3] = "-appsecret:"+GUID.randomvalue() ;
		args[4] = "-name:NooLabNativeBrowser" ;
		args[5] = "-loglevel:2" ;
		// 

		return args;
		
	}
	
	
	private String[] getArgumentsBase() {
		// 
		String[] args = new String[8];
		
		// these should all come from a resources config file...
		args[0] = "-homedir:\""+homepath+"\"" ; 
		args[1] = "-any" ;
		args[2] = "-flavor:pos,$lang$" ;  // pos == PoSTagger, else: parse
		
		args[3] = "-context:itexx" ;
		args[4] = "-comm: in-$in$,out-$out$" ;
		args[5] = "-appsecret:"+GUID.randomvalue() ;
		args[6] = "-name:NooLabNLP" ;
		args[7] = "-loglevel:2" ;
		// args[7] = ""; // -onCompletion:exit" ;

		return args;
	}
	
	
	
	
	public AppCommBox prepareTcpBox( int oport, int iport, String name) {
		
		int result=-1 ;
		int cport=-1;
		
		AppCommBox commBox=null;
		
		try{
			commBox = new AppCommBox( name,iport, oport ) ;
			result = 0;
			cport = commBox.getInPort();
			
		}catch(Exception e){
			result = -7;
			String estr = e.getMessage();
			out.printErr(1, estr+"\nApplication will stop.\n");
			
			// (new CommBoxMessage()).issueTcpMessage( commBox.tcpBox.getTcpSender() , CommBoxMessageIntf._MSG_STOPPING, app.processName, app.secret) ;
		
			out.delay(500);
			System.exit(-9) ;
		}

		if (result>=0){
			out.delay(200) ;
			out.print(2, "ProcessAdmin started a Communication port, listening on port <"+cport+"> ...");
		}
		return commBox;
	}


	public AppCommBox getTcpBox(String langCode) {
		// 
		String mstr = "ling-"+langCode ;
		AppCommBox cbox = null;
		
		
		for (int i=0;i<commBoxes.length;i++){
			if (commBoxes[i]==null){
				continue;
			}
			String boxname = commBoxes[i].getName();
			if (boxname.contentEquals(mstr)){
				cbox = commBoxes[i];
				break;
			}
		}
		return cbox;
	}

	
	private int checkForFreePort(int basePort) {
		// 
		// try to create a server...
		
		// if not possible, select another port
		
		// destroy it again
		return basePort;
	}

	
	class ProcessSupervisor implements Runnable{

		Thread psvThrd ;
		int z=0;
		
		public ProcessSupervisor(ProcessAdmin processAdmin) {

			psvThrd = new Thread(this,"psvThrd");
			psvThrd.start();
		}

		@Override
		public void run() {
			supervisorProcessIsRunning = true;
			int state ;
			
			
			while (supervisorProcessIsRunning){
				out.delay(1000);
				
				for (int i=0;i<nlpProc.length;i++){
					if (nlpProc[i]==null){
						continue;
					}
					state = nlpProc[i].getProcessState();
					if ( (state== ProcessAdminIntf._CO_STATUS_STOPPED) || 
						 (state== ProcessAdminIntf._CO_STATUS_CLOSED)){
						// inform main application
						appCommons.setInformationAboutSubProcess( i,state,nlpProc[i].getGuid() ) ;
						z++;
						if (z<20){
							appCommons.getApp().close();
						}
					}
				}
			}
			
			supervisorProcessIsRunning = false;
		}
		
		
	}
	
	
}



class StreamGobbler2 extends Thread {
	InputStream is;
	String type;

	StreamGobbler2(InputStream is, String type) {
		this.is = is;
		this.type = type;
	}

	public void run() {
		
		try {
			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line = null;
			while ((line = br.readLine()) != null) {
				System.out.println(type + "[x] " + line);
			}

		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}